﻿using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using AzureDiagMon.Common.Dtos;
using AzureDiagMon.Common.Helpers.Azure;
using Microsoft.WindowsAzure.StorageClient;

namespace AzureDiagMon.Services.Azure
{
    public class WadLogEntryDataContext : GenericTableServiceContext<WadLogEntryTableEntity>
    {
        const String TickFormat = "D19"; // 19-digit, 0-padded

        public WadLogEntryDataContext(TableServiceContext context, string tableName) : base(context, tableName) { }

        public CloudTableQuery<TEntity> GetDiagnosticEntities<TEntity>(DateTime startTime, DateTime endTime,
                                                                       String deploymentId = null,
                                                                       String roleName = null,
                                                                       String roleInstanceId = null)
            where TEntity : WadLogEntryTableEntity
        {
            return GetDiagnosticEntities<TEntity>(startTime, endTime, new[] { AzureDiagnosticsRowKey() });
        }

        public CloudTableQuery<TEntity> GetDiagnosticEntities<TEntity>(DateTime? fromTime, DateTime? toTime, IEnumerable<string> includeRowKeys = null, IEnumerable<string> excludeRowKeys = null)
            where TEntity : WadLogEntryTableEntity
        {
            return GetDiagnosticEntities<TEntity>(fromTime.HasValue ? DateTimeToUtcTicks(fromTime.Value) : (long?)null,
                                                  toTime.HasValue ? DateTimeToUtcTicks(toTime.Value) : (long?)null,
                                                  includeRowKeys,
                                                  excludeRowKeys);
        }

        public CloudTableQuery<TEntity> GetDiagnosticEntities<TEntity>(long? fromKey, long? toKey, IEnumerable<string> includeRowKeys = null, IEnumerable<string> excludeRowKeys = null)
            where TEntity : WadLogEntryTableEntity
        {
            Context.MergeOption = MergeOption.NoTracking;

            var q = Context.CreateQuery<TEntity>(TableName);
            var filter = BuildFilter(fromKey, toKey, includeRowKeys, excludeRowKeys);
            Debug.WriteLine(filter);
            q = q.AddQueryOption("$filter", filter);
            //q = q.AddQueryOption("$orderby", "PartitionKey desc, RowKey desc");
            return q.AsTableServiceQuery();
        }

        #region Filter helpers

        private static string BuildFilter(long? fromKey, long? toKey, IEnumerable<string> includeRowKeys, IEnumerable<string> excludeRowKeys)
        {
            var sb = new StringBuilder();

            string from = null;
            string to = null;

            var includeFilter = BuildRowKeyFilter(includeRowKeys);
            var eFilter = BuildRowKeyFilter(excludeRowKeys);
            var excludeFilter = String.IsNullOrEmpty(eFilter) ? String.Empty : String.Format("(not {0})", eFilter);

            if (fromKey.HasValue)
                from = LongToPartitionKey(fromKey.Value - 1);
            if (toKey.HasValue)
                to = LongToPartitionKey(toKey.Value);

            sb.Append(includeFilter);
            sb.Append(excludeFilter);

            if (sb.Length > 0)
                sb.Append(" and");

            sb.Append(" (");

            //All items included from the 1st partitionkey)
            //if (fromKey.HasValue)
            //  sb.Append(String.Format("(PartitionKey eq '{0}') or ", from));

            sb.Append(String.Format("((PartitionKey gt '{0}') and (PartitionKey lt '{1}'))", from, to));

            sb.Append(")");

            return sb.ToString();
        }

        private static string BuildRowKeyFilter(IEnumerable<string> rowKeys)
        {
            if (rowKeys == null)
                return String.Empty;

            return String.Format("({0})",
                String.Join(" or ",
                    rowKeys.Select(f =>
                        String.Format("((RowKey ge '{0}') and (RowKey lt '{1}'))", f, GetNextKey(f)))));
        }
        private const string RowKeyItemsSeparator = "___";

        private static String AzureDiagnosticsRowKey(string deploymentId = null,
                                                     string roleName = null,
                                                     string roleInstanceId = null)
        {
            var sb = new StringBuilder();

            if (!String.IsNullOrWhiteSpace(deploymentId))
            {
                sb.Append(deploymentId).Append(RowKeyItemsSeparator);
                if (!String.IsNullOrWhiteSpace(roleName))
                {
                    sb.Append(roleName).Append(RowKeyItemsSeparator);
                    if (!String.IsNullOrWhiteSpace(roleInstanceId))
                        sb.Append(roleInstanceId);
                }
            }

            return sb.ToString();
        }

        private static String GetNextKey(String @string)
        {
            var sb = new StringBuilder(@string);
            sb[sb.Length - 1]++;
            return sb.ToString();
        }

        private static String DateTimeToPartitionKey(DateTime dt)
        {
            //dt = dt.AddSeconds(-dt.Second);
            dt = dt.ToUniversalTime();
            return dt.Ticks.ToString(TickFormat);
        }

        private static long DateTimeToUtcTicks(DateTime dt)
        {
            dt = dt.ToUniversalTime();
            return dt.Ticks;
        }

        private static String LongToPartitionKey(long ticks)
        {
            return ticks.ToString(TickFormat);
        }


        #endregion
    }
}
